package contegor.scire.structure;

import java.lang.reflect.Constructor;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import contegor.scire.core.CommitTask;
import contegor.scire.core.Container;
import contegor.scire.core.ContainerImpl;
import contegor.scire.core.DuplicateElementException;
import contegor.scire.core.Id;
import contegor.scire.core.IdFragment;
import contegor.scire.core.Identifiable;
import contegor.scire.core.MissingElementException;
import contegor.scire.core.ReturnPoint;
import contegor.scire.core.ReturnPointWaitingObserver;
import contegor.scire.core.RollBackTask;
import contegor.scire.core.SetExternalLinksToTrack;
import contegor.scire.core.StorageResultsReturnPoint;
import contegor.scire.core.Task;
import contegor.scire.core.TaskId;
import contegor.scire.core.Transaction;
import contegor.scire.core.TransactionId;
import contegor.scire.core.Version;
import contegor.scire.core.WrongStateException;

/**
 * Abstract {@link ConceptCloud} implementation.
 *
 * @version $Revision$
 * @author  Héctor Cirbián Pousa ($Author$)
 */
@Version("$Id$")
public abstract class AbstractConceptCloud implements ConceptCloud {

  protected static Transaction PRIVATE_TRANSACTION = new Transaction(true);

  protected class CloudContainerImpl extends ContainerImpl {
    private static final long serialVersionUID = 5132691487738187983L;

    public CloudContainerImpl(long creationTime, TransactionId transactionId, IdFragment idFragment) {
      super(creationTime, transactionId, idFragment);
    }

    protected Identifiable getIdentifiable(IdFragment id) {
      return this.identifiables.get(id);
    }
 
    protected Iterable<Container> localContainers() {
      return new Iterable<Container>() {
        @Override
        public Iterator<Container> iterator() {
          return new Iterator<Container>() {
            
            Container next = null;
            Iterator<Identifiable> identifiables = CloudContainerImpl.this.identifiables.values().iterator();

            public boolean hasNext() {
              if (next != null)
                return true;
              while (identifiables.hasNext()) {
                Identifiable i = identifiables.next();
                if (i instanceof Container) {
                  next = (Container)i;
                  return true;
                }
              }
              return false;
            }

            public Container next() {
              if (hasNext()) {
                Container aux = next;
                next = null;
                return aux;
              }
              else
                return null;
            }

            public void remove() {
              throw new UnsupportedOperationException("Non modifiable iterator.");
            }
          };
        }
      };
    }

    public Collection<Identifiable> getContent() {
      return Collections.unmodifiableCollection(identifiables.values());
    }

    public <R> void route(Task<R> task, ReturnPoint<? super R> rp) {
      if (this.getId().sameRoot(task.getTargetContainerId()))
        super.route(task, rp);
      else
        dock.submit(task, rp);
    }

    protected void clearTransaction() {
      this.status.set(Status.UNTOUCHED);
      this.transactionId.set(null);
    }
  }

  protected class CloudRootContainerImpl extends CloudContainerImpl {
    private static final long serialVersionUID = -1695333811122756458L;

    public CloudRootContainerImpl(long creationTime, TransactionId transactionId, IdFragment idFragment) {
      super(creationTime, transactionId, idFragment);
    }

    @Override
    public void setContainer(Container container) throws IllegalArgumentException {
      throw new UnsupportedOperationException();
    }
  }

  protected CloudRootContainerImpl  root;
  protected transient DockingModule dock;

  public AbstractConceptCloud() {
    this.root = null;
    this.dock = null;
  }

  protected Container find(Id id) {
    if (!this.root.getLocalIdFragment().equals(id.getIdFragment(0)))
      return null;
    Iterator<IdFragment> iter = id.getIdFragmentsList().iterator();
    Container actual = this.root;
    iter.next();
    while (iter.hasNext()) {
      Identifiable aux = actual.getConcept(iter.next()); // With current implementation, Containers are Concepts.
      if ((aux == null) || !(aux instanceof Container))
        return null;
      else
        actual = (Container)aux;
    }
    return actual;
  }

  protected void searchLinkTrackingTasksFor(Id root, Container container, List<Task<Void>> tasks) {
    if (container instanceof CloudContainerImpl)
      for (Container child: ((CloudContainerImpl)container).localContainers())
        searchLinkTrackingTasksFor(root, child, tasks);
    for (Map.Entry<Id, Integer> entry: container.getNumberOfLinksTo(root).entrySet())
      tasks.add(new SetExternalLinksToTrack(PRIVATE_TRANSACTION.nextTaskId(), entry.getKey().getParentId(), entry.getKey().getLastIdFragment(), container.getId(), entry.getValue()));
  }

  @Override
  public List<Task<Void>> getLinkTrackingTasksFor(Id root) {
    List<Task<Void>> tasks = new LinkedList<Task<Void>>();
    searchLinkTrackingTasksFor(root, this.root, tasks);
    return tasks;
  }

  protected void stopTrackingLinksOfImpl(Id root, Container container) {
    container.untrackExternalLinks(root);
    if (container instanceof CloudContainerImpl)
      for (Container child: ((CloudContainerImpl)container).localContainers())
        stopTrackingLinksOfImpl(root,child);
  }

  @Override
  public void stopTrackingLinksOf(Id root) {
    stopTrackingLinksOfImpl(root, this.root);  
  }

  @Override
  public Id getRootId() {
    return this.root.getId();
  }

  @Override
  public void createContainer(Id id) throws MissingElementException, DuplicateElementException, WrongStateException {
    Container parent = find(id.getParentId());
    if (parent == null)
      throw new MissingElementException(MessageFormat.format("The element identified by {0} does not exists.", id));
    if (!(parent instanceof ContainerImpl))
      throw new WrongStateException("Can't add containers to " + parent.getId());
    try {
      CloudContainerImpl container = new CloudContainerImpl(System.currentTimeMillis(), PRIVATE_TRANSACTION.getTransactionId(), id.getLastIdFragment());
      ((ContainerImpl)parent).incorporateContainer(container);
      container.clearTransaction();
    }
    catch(WrongStateException ex) { assert false; /* Execution should never reach this point! */ }
  }

  @Override
  public void dock(DockingModule dockingModule) throws WrongStateException {
    if (dock != null)
      throw new WrongStateException("Hook all ready stablished.");
    dockingModule.open(this);
    this.dock = dockingModule;
  }

  @Override
  public void release(DockingModule dockingModule) throws WrongStateException {
    if (dock == dockingModule) {
      dock.close(this);
      dock = null;
    }
    else
      throw new WrongStateException("There is not such hook.");
  }

  @Override
  public void commit(TaskId taskId) throws Exception {
    onAllContainers(taskId, CommitTask.class.getConstructor(TaskId.class, Id.class));
  }

  @Override
  public void rollback(TaskId taskId) throws Exception {
    onAllContainers(taskId, RollBackTask.class.getConstructor(TaskId.class, Id.class));
  }

  @Override
  public Map<Id, Set<TransactionId>> getPendingTransactions() {
    Map<Id, Set<TransactionId>> result = new TreeMap<Id, Set<TransactionId>>();
    getPendingTransactionsImpl(root, result);
    return result;
  }

  private void getPendingTransactionsImpl(Container container, Map<Id, Set<TransactionId>> result) {
    if (container instanceof CloudContainerImpl)
      for (Container child: ((CloudContainerImpl)container).localContainers())
        getPendingTransactionsImpl(child, result);
    Set<TransactionId> transactions = new TreeSet<TransactionId>(container.getPendingTransactions());
    if (transactions.size() > 0)
      result.put(container.getId(), transactions);
  }

  private <T extends Task<R>,R> void onAllContainers(TaskId taskId, Constructor<T> constructor) throws Exception {
    List<Id> containerIds = new LinkedList<Id>();
    getAllContainerIdentifiers(this.root, containerIds);
    StorageResultsReturnPoint<R> srp = new StorageResultsReturnPoint<R>();
    ReturnPointWaitingObserver wo = new ReturnPointWaitingObserver(containerIds.size());
    srp.setObserver(wo);
    for (Id id: containerIds) {
      this.process(constructor.newInstance(taskId.next(true), id), srp);
    }
    wo.await();
    if ((srp.exceptions != null) && (srp.exceptions.size()>0))
      throw srp.exceptions.get(0);    
  }
  
  private static void getAllContainerIdentifiers(Container container, List<Id> ids) {
    ids.add(container.getId());
    if (container instanceof CloudContainerImpl)
      for (Container child: ((CloudContainerImpl)container).localContainers())
          getAllContainerIdentifiers(child, ids);
  }

  /*
  private static <T> void repeatTaskOnAllChilds(TaskId taskId, CloudContainerImpl root, Constructor<Task<T>> constructor) throws Exception {
    StorageResultsReturnPoint<T> srp = new StorageResultsReturnPoint<T>();
    Task<T> task = constructor.newInstance(taskId.next(true), root.getId());
    root.process(task, srp);
    if ((srp.exceptions != null) && (srp.exceptions.size()>0))
      throw srp.exceptions.get(0);
    for (Identifiable identifiable: root.getContent())
      if (identifiable instanceof CloudContainerImpl)
        repeatTaskOnAllChilds(taskId, (CloudContainerImpl)identifiable, constructor);
  }
  */
}
